﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Coding4Fun.Kinect.Wpf;
using Epitech.Krisk.Kinect.Cursor;
using Epitech.Krisk.Kinect.Sensor.Exception;
using Epitech.Krisk.Kinect.Sensor.Gestures.Navigation;
using Epitech.Krisk.Kinect.Sensor.Gestures.Swipes;
using Epitech.Krisk.Kinect.Sensor.Postures;
using Epitech.Krisk.Kinect.Sensor.Stability;
using Epitech.Krisk.Kinect.Sensor.Tools;
using Epitech.Krisk.Kinect.Sensor.Zoom;
using Microsoft.Research.Kinect.Nui;

namespace Epitech.Krisk.Kinect.Sensor
{
    public class SensorManager : INotifyPropertyChanged
    {
        #region Fields
        
        private Runtime _nui;
        private bool _isInit;
        private SensorTools _tools;
        private byte[] _convertedDepthFrame;
        private bool _startCursor;
        private KinectCursor _cursor;
        private int _screenWidth;
        private int _screenHeight;
        private BarycenterCalculator _bCalculator = new BarycenterCalculator();
        private bool _navigationActivated = false;
        private bool _swipeActivated = false;
        private bool _pausePostureActivated = false;
        #endregion

        #region Properties

        /// <summary>
        /// Si un click de souris est lance
        /// </summary>
        public event EventHandler MouseClick;

        private BitmapSource _depthBitmapSource;
        /// <summary>
        /// L'image de Depth.
        /// </summary>
        public BitmapSource DepthBitmapSource
        {
            get { return _depthBitmapSource; }
            set
            { _depthBitmapSource = value;
            OnPropertyChanged("DepthBitmapSource");}
        }

        private ZoomManager _zoomManager;
        /// <summary>
        /// Propriete Zoom Manager
        /// </summary>
        public ZoomManager ZoomManager
        {
            get { return _zoomManager; }
        }

        /// <summary>
        /// evenement lance s'il n'y a personne devant l'ecran
        /// </summary>
        public event EventHandler<PeopleDetectorEventArgs> PeopleDetector; 

        /// <summary>
        /// L'indice de confiance pour les joints Kinect.
        /// </summary>
        public float TrustIndex { get; set; }

        /// <summary>
        /// True si personne n'est devant l'ecran.
        /// </summary>
        public bool NobodyInFrontOfScreen;

        /// <summary>
        /// Enum recensant toutes les actions possibles.
        /// </summary>
        public enum StartAction
        {
            Cursor,
            Navigation,
            Swipe,
            PausePosture,
            Zoom
        };

        private LeftHandSwipe _lSwipe = new LeftHandSwipe(20, 1);
        /// <summary>
        /// Permet d'acceder a l'objet de swipe pour la main gauche.
        /// </summary>
        public LeftHandSwipe LSwipe
        {
            get { return _lSwipe; }
        }

        private RightHandSwipe _rSwipe = new RightHandSwipe(20, 1);
        /// <summary>
        /// Permet d'acceder a l'objet de swipe pour la main droite.
        /// </summary>
        public RightHandSwipe RSwipe
        {
            get { return _rSwipe; }
        }

        private NavigationGesture _navigation;
        private PausePosture _pausePosture = new PausePosture();

        /// <summary>
        /// Objet de posture de pause.
        /// </summary>
        public PausePosture PausePosture
        {
            get { return _pausePosture; }
        }

        /// <summary>
        /// Objet de Navigation
        /// </summary>
        public NavigationGesture Navigation
        {
            get { return _navigation; }
        }

        #endregion

        #region SensorManager.Constructor()

        private static SensorManager _instance;
        private bool _zoomActivated;
        private double MaxRange = 0.25f;

        public static SensorManager Instance
        {
            get { return _instance ?? (_instance = new SensorManager(0.8f)); }
        }

        private SensorManager(float trustIndex = 0.9f)
        {
            TrustIndex = trustIndex;
             _navigation = new NavigationGesture(TrustIndex);
            Initialize();
        }

        #endregion

        #region SensorManager.Initialize()


        /// <summary>
        /// Initialise la Kinect.
        /// </summary>
        private void Initialize()
        {
                foreach (Runtime kinect in Runtime.Kinects)
                {
                    if (kinect.Status == KinectStatus.Connected)
                    {
                        _nui = kinect;
                        break;
                    }
                }
                //if (Runtime.Kinects.Count == 0)
                //    throw new KinectException("No Kinect Found");
                StartInit();
        }

        private void StartInit()
        {
            if (_nui == null)
                return;

            try
            {
                _nui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex | RuntimeOptions.UseSkeletalTracking);
                _isInit = true;
                _nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);
                _nui.NuiCamera.ElevationAngle = 10;
            }
            catch (System.Exception)
            {
                throw new KinectException("Unable to initialize the Kinect Camera.");
            }

            _isInit = true;
            _nui.DepthFrameReady += NuiDepthFrameReady;
            _nui.SkeletonFrameReady += NuiSkeletonFrameReady;
            _nui.SkeletonEngine.TransformSmooth = true;
            var parameters = new TransformSmoothParameters
            {
                Smoothing = 1.0f,
                Correction = 0.1f,
                Prediction = 0.1f,
                JitterRadius = 0.05f,
                MaxDeviationRadius = 0.05f
            };
            _nui.SkeletonEngine.SmoothParameters = parameters;
            _tools = new SensorTools();
            _screenWidth = SystemInformation.PrimaryMonitorSize.Width;
            _screenHeight = SystemInformation.PrimaryMonitorSize.Height;
            _cursor = new KinectCursor(KinectCursor.CursorPrecision.Normal, TrustIndex, _screenWidth, _screenHeight);
            _zoomManager = new ZoomManager();
        }

        #endregion

        #region SensorManger.UnInitialize()

        /// <summary>
        /// Desactive la kinect et supprime les objets.
        /// </summary>
        public void UnInitialize()
        {
            if (_isInit)
                _nui.Uninitialize();
        }

        #endregion

        #region SensorManager.StartAction()

        /// <summary>
        /// Demmarre les differentes actions de la Kinect.
        /// </summary>
        /// <param name="action">l'action a demarrer</param>
        public void StartSensorAction(StartAction action)
        {
            switch (action)
            {
                case StartAction.Cursor:
                    _startCursor = true;
                    break;
                case StartAction.Navigation:
                    _navigationActivated = true;
                    break;
                case StartAction.Swipe:
                    _swipeActivated = true;
                    break;
                case StartAction.PausePosture:
                    _pausePostureActivated = true;
                    break;
                case StartAction.Zoom:
                    _zoomActivated = true;
                    break;
            }
        }

        #endregion

        #region SensorManager.StopAction()
        /// <summary>
        /// Arrete les actions de la Kinect.
        /// </summary>
        /// <param name="action">l'action a arreter</param>
        public void StopSensorAction(StartAction action)
        {
            switch (action)
            {
                case StartAction.Cursor:
                    _startCursor = false;
                    break;
                case StartAction.Navigation:
                    _navigationActivated = false;
                    break;
                case StartAction.Swipe:
                    _swipeActivated = false;
                    break;
                case StartAction.PausePosture:
                    _pausePostureActivated = false;
                    break;
                    case StartAction.Zoom:
                    _zoomActivated = false;
                    break;
            }

        }
        #endregion

        #region Event

        private void NuiSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            SkeletonFrame allSkeletons = e.SkeletonFrame;
            SkeletonData skeleton =
                allSkeletons.Skeletons.FirstOrDefault(s => s.TrackingState == SkeletonTrackingState.Tracked);
            if (skeleton != null)
            {
//                Debug.WriteLine(skeleton.TrackingState);
                if (skeleton.TrackingState != SkeletonTrackingState.Tracked)
                {
                    NobodyInFrontOfScreen = true;
                    PeopleDetector.Invoke(this, new PeopleDetectorEventArgs(skeleton.TrackingState));
                    return;
                }
                NobodyInFrontOfScreen = false;
                if (!ZoomManager.IsZoomActive)
                {

                    if (_startCursor && IsLeftHandOverHead(skeleton))
                    {
                        _cursor.StartCursor(skeleton, JointID.HandRight);
                        _cursor.LetsClick += () => MouseClick(this, EventArgs.Empty);
                    }

                    _bCalculator.Add(skeleton.Position.ToVector3X(), skeleton.TrackingID);
                    if (!_bCalculator.IsStable(skeleton.TrackingID))
                        return;

                    if (_navigationActivated && !IsLeftHandOverHead(skeleton))
                    {
                        SearchForSelection(skeleton);
                        if (skeleton.Joints[JointID.HandRight].TrackingState == JointTrackingState.Tracked &&
                            skeleton.Joints[JointID.ShoulderRight].TrackingState == JointTrackingState.Tracked)
                            _navigation.PushClick(skeleton.Joints[JointID.HandRight],
                                                  skeleton.Joints[JointID.ShoulderRight]);
                    }

                    if (_swipeActivated && !IsLeftHandOverHead(skeleton))
                        SearchForSwipe(skeleton);

                    if (_pausePostureActivated)
                        _pausePosture.Scan(skeleton);
                }

                if (!IsLeftHandOverHead(skeleton) || _zoomActivated)
                    ZoomManager.AnalyzeZoom(skeleton);


            }

        }

        private void NuiDepthFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            PlanarImage image = e.ImageFrame.Image;

            _convertedDepthFrame = _tools.ConvertDepthFrame(image.Bits);
            DepthBitmapSource = BitmapSource.Create(image.Width, image.Height, 96, 96, PixelFormats.Bgra32, null,
                                                       _convertedDepthFrame, image.Width * 4);
        }

        #endregion

        #region Private Methods

        private bool IsLeftHandOverHead(SkeletonData skeleton)
        {
            Vector? head = null;
            Vector? leftHand = null;
            if (skeleton.Joints[JointID.Head].TrackingState == JointTrackingState.Tracked
                && skeleton.Joints[JointID.HandLeft].TrackingState == JointTrackingState.Tracked)
            {
                head = skeleton.Joints[JointID.Head].Position;
                leftHand = skeleton.Joints[JointID.HandLeft].Position;
            }
            return CheckHandOverHead(head, leftHand);
        }

        bool CheckHandOverHead(Vector? headPosition, Vector? handPosition)
        {
            if (!handPosition.HasValue || !headPosition.HasValue)
                return false;

            if (handPosition.Value.Y < headPosition.Value.Y)
                return false;

            if (Math.Abs(handPosition.Value.X - headPosition.Value.X) > MaxRange)
                return false;

            if (Math.Abs(handPosition.Value.Z - headPosition.Value.Z) > MaxRange)
                return false;

            return true;
        }


        //Looking for Selection
        private void SearchForSelection(SkeletonData skeleton)
        {
            foreach (Joint joint in skeleton.Joints)
            {
                if (joint.Position.W < TrustIndex || joint.TrackingState != JointTrackingState.Tracked)
                    continue;
                switch (joint.ID)
                {
                    case JointID.HandRight:
                        _navigation.AddJoint(joint.Position);
                        break;
                }
            }
        }

        //Looking for Swipe
        //Raise an event if detected
        private void SearchForSwipe(SkeletonData skeleton)
        {
            foreach (Joint joint in skeleton.Joints)
            {
                if (joint.Position.W < TrustIndex || joint.TrackingState != JointTrackingState.Tracked)
                    continue;
                switch (joint.ID)
                {
                    case JointID.HandLeft:
                        _lSwipe.AddJoint(joint.Position, _nui.SkeletonEngine);
                        break;
                    case JointID.HandRight:
                        _rSwipe.AddJoint(joint.Position, _nui.SkeletonEngine);
                        break;
                }
            }
        }

        #endregion

        #region INotifyPropertyChanged

        /// <summary>
        /// Evénement déclenché lorsqu'un propriété change.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Déclenche l'événement <see cref="PropertyChanged"/>.
        /// </summary>
        /// <param name="propertyName">Le nom de la propriété.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion


    }
}
